#include "plotter.h"
#include "global.h"

//////////////////////////////////////////////////////////////////////////////


QPalette Plotter::colorTheme(const QColor &base) const
{
    const QColor background = base;
    const QColor foreground = base.dark(233);

    const QColor mid = base.dark(110);
    //const QColor dark = base.dark(170);
    const QColor dark = Qt::darkGray;
    const QColor light = dark.light(120);
    const QColor text = QColor(Qt::white);

    QPalette palette;
    for ( int i = 0; i < QPalette::NColorGroups; i++ )
    {
        QPalette::ColorGroup cg = (QPalette::ColorGroup)i;

        palette.setColor(cg, QPalette::Base, base);
        palette.setColor(cg, QPalette::Window, background);
        palette.setColor(cg, QPalette::Mid, mid);
        palette.setColor(cg, QPalette::Light, light);
        palette.setColor(cg, QPalette::Dark, dark);
        palette.setColor(cg, QPalette::Text, text);
        palette.setColor(cg, QPalette::WindowText, foreground);
    }

    return palette;
}

Plotter::Plotter(QWidget *parent) : QWidget(parent)
{
    // QWidget::setBackgroundRole() define el pincel de la paleta usado para
    // renderizar el background del widget a QPalette::Dark
//    setPalette(colorTheme(QColor(Qt::darkCyan)));

    color_linea=global::color_linea;
    Titulo="f(x) Vs x";
    Xlabel="x";
    Ylabel="f(x)";
    setPalette(colorTheme(global::color_fondo));

    //setBackgroundRole(QPalette::Dark);

    // QWidget::setAutoFillBackground() rellena automaticamente el background
    // del widget antes de invocar a paintEvent
    //setAutoFillBackground(true);

    // La politica QSizePolicy::Expanding indica que el widget esta
    // especialmente dispuesto a crecer por encima de su sizeHint(), aunque
    // tambien podria encoger si fuera necesario. Es una configuracion tipica
    // para widgets que emplean mucho espacio en pantalla
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    // Cuando el usuario pulsa el teclado, el evento keyPressEvent se envia
    // primero al widget que tiene el foco. QWidget::setFocusPolicy() establece
    // la forma en la que el widget acepta el foco del teclado. La politica por
    // defecto es Qt::NoFocus que hace que el widget no acepte ningun foco. La
    // policita Qt::StrongFocus hace que el widget acepte el foco tabulando y
    // clickando con el raton
    setFocusPolicy(Qt::StrongFocus);

    rubberBandIsShown = false;

    // Los QToolButton son un tipo especial de boton que proporcionan acceso
    // rapido a las opciones que definen. Los QToolButton muestran un icono en
    // lugar de la etiqueta mostrada por los QPushButton. Puesto que no se usa
    // ningun layout manager para reposicionar estos botones (los posicionamos
    // manualmente en el evento resizeEvent), debe especificarse el padre del
    // widget explicitamente pasando this al constructor
    zoomInButton = new QToolButton(this);
    zoomOutButton = new QToolButton(this);

    // QToolButton::setIcon() establece el icono mostrado en el boton
    zoomInButton->setIcon(QIcon(":/Imagenes/Imagenes/zoomin.png"));
    zoomOutButton->setIcon(QIcon(":/Imagenes/Imagenes/zoomout.png"));

    // QWidget::adjustSize() ajusta el tamaño del widget para encajar su icono
    zoomInButton->adjustSize();
    zoomOutButton->adjustSize();

    // Se conecta el click de los botones con sus respectivos slot
    connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));
    connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));

    setPlotSettings(PlotSettings());
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::setPlotSettings(const PlotSettings &settings)
{
    // QVector::clear() borra todos los elementos del vector zoomStack
    zoomStack.clear();

    // QVector::append() inserta settings al final del vector zoomStack
    zoomStack.append(settings);

    curZoom = 0;

    // QWidget::hide() mantiene ocultos a los widget QToolButton hasta que se
    // llama explicitamente a QWidget::show() desde los slot zoomIn y zoomOut
    zoomInButton->hide();
    zoomOutButton->hide();

    refreshPixmap();
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
    curveMap[color_linea] = data;
    refreshPixmap();
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::clearCurve(int id)
{
    curveMap.remove(id);
    refreshPixmap();
}



//////////////////////////////////////////////////////////////////////////////
QSize Plotter::minimumSizeHint() const
{
    return QSize(6 * Margin, 4 * Margin);
}



//////////////////////////////////////////////////////////////////////////////
QSize Plotter::sizeHint() const
{
    return QSize(12 * Margin, 8 * Margin);
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::updateRubberBandRegion()
{
    QRect rect = rubberBandRect.normalized();

    // QWidget::update(x,y,w,h) planifica un evento paintEvent para redibujar
    // la zona del widget definida por el rectangulo (x,y,w,h)

    // lado superior de la goma elastica
    update(rect.left(),  rect.top(),    rect.width(), 1);
    // lado inferior de la goma elastica
    update(rect.left(),  rect.bottom(), rect.width(), 1);
    // lado izquierdo de la goma elastica
    update(rect.left(),  rect.top(),    1,            rect.height());
    // lado derecho de la goma elastica
    update(rect.right(), rect.top(),    1,            rect.height());
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::refreshPixmap()
{
    // QWidget::size() devuelve un QSize con el tamaño del widget.
    // QPixmap::QPixmap(QSize) construye un pixmap del mismo tamaño del widget
    pixmap = QPixmap(size());

    // QPixmap::fill(widget,x,y) rellena el pixmap con el color de background
    // del widget. El punto (x,y) define un offset respecto de la coordenada
    // superior izquierda del widget en el que sera mapeado el pixel superior
    // izquierdo del pixmap
    pixmap.fill(this, 0, 0);

    // Construimos un QPainter que permita dibujar sobre el pixmap
    QPainter painter(&pixmap);

    // QPainter::initFrom(widget) inicializa lapiz, background y fuente del
    // QPainter a los mismos que el widget dado
    painter.initFrom(this);

    // Se dibuja la rejilla de los ejes de coordenadas
    drawGrid(&painter);

    // Se dibujan las curvas
    drawCurves(&painter);

    update();
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::drawGrid(QPainter *painter)
{
    // rect se corresponde con el rectangulo donde se dibuja el Plotter,
    // enmarca a los ejes de coordenadas
    QRect rect(Margin, Margin, width() - 2*Margin, height() - 2*Margin);

    // QRect::isValid() devuelve true si el rectangulo es valido, es decir,
    // si left()<right(), top()<bottom() y el rectangulo es no vacio
    if (!rect.isValid())
        return;

    PlotSettings settings = zoomStack[curZoom];

    QPen quiteDark = palette().dark().color().light();

    QPen light = palette().light().color();

    // Bucle que dibuja las lineas verticales de la rejilla y las
    // marcas del eje X
    for (int i = 0; i <= settings.numXTicks; ++i)
    {

        // Posicion de la i-esima marca en el eje X
        int x = rect.left() + (i * (rect.width() - 1) / settings.numXTicks);

        // Etiqueta que acompaña a la i-esima marca del eje X
        double label = settings.minX +
                       (i * settings.spanX() / settings.numXTicks);

        // Se dibuja una linea vertical fijando x con paleta dark
        painter->setPen(quiteDark);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());

        // Se dibuja una linea vertical fijando x con paleta light
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);

        // QPainter::drawText(x,y,w,h,flags,label) dibuja el texto label dentro
        // del rectangulo (x,y,w,h), donde flags es una mascara de bits que
        // indica la alineacion del texto dentro del rectangulo
        painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
                          Qt::AlignHCenter | Qt::AlignTop, QString::number(label));
    }
    //xlabel
    painter->drawText(rect.width()/2, rect.top() - 30, 200, 15, Qt::AlignHCenter | Qt::AlignTop,Titulo);
    painter->drawText(2, rect.top() - 30, 200, 15, Qt::AlignLeft | Qt::AlignTop,Ylabel);
    painter->drawText(rect.width()/2, rect.bottom() + 20, 200, 15, Qt::AlignHCenter | Qt::AlignTop,Xlabel);
    // Bucle que dibuja las lineas horizontales de la rejilla y las
    // marcas del eje Y
    for (int j = 0; j <= settings.numYTicks; ++j)
    {

        // Posicion de la j-esima marca en el eje Y
        int y = rect.bottom() - (j * (rect.height() - 1) / settings.numYTicks);

        // Etiqueta que acompaña a la j-esima marca del eje Y
        double label = settings.minY +
                       (j * settings.spanY() / settings.numYTicks);

        // Se dibuja una linea horizontal fijando y con paleta dark
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);

        // Se dibuja una linea horizontal fijando y con paleta ligth
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);

        // QPainter::drawText(x,y,w,h,flags,text) dibuja el texto text dentro
        // del rectangulo (x,y,w,h), donde flags es una mascara de bits que
        // indica la alineacion del texto dentro del rectangulo
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter, QString::number(label));
    }

    // Se resalta con paleta light el rectangulo de los ejes de coordenadas
    painter->drawRect(rect);
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::drawCurves(QPainter *painter)
{
    // Vector de colores para representar las distintas curvas
    static const QColor colorForIds[8] =
    { Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow, Qt::red,Qt::black,Qt::white };

    PlotSettings settings = zoomStack[curZoom];

    // rect se corresponde con el rectangulo donde se dibuja el Plotter,
    // enmarca a los ejes de coordenadas
    QRect rect(Margin, Margin, width() - 2*Margin, height() - 2*Margin);

    if (!rect.isValid())
        return;

    // El clipping es una tecnica que consiste en definir una region de recorte
    // para la pantalla de tal forma que todo lo que se dibuja fuera de esa
    // zona no se muestra por pantalla. QPainter::setClipRect(QRect) establece
    // la region de recorte (clipping) al area definida por el rectangulo dado
    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));

    // QMapIterator::QMapIterator define un iterador al estilo Java para el
    // QMap curveMap y lo inicializa apuntando antes del primer elemento
    QMapIterator <int, QVector<QPointF> > i(curveMap);

    // Se itera sobre todas las curvas
    while (i.hasNext())
    {

        // QMapIterator::next() devuelve el siguiente elemento y avanza el
        // iterador en una posicion
        i.next();

        // QMapIterator::key() devuelve la clave del item actual del QMap
        int id = i.key();

        // QMapIterator::value() devuelve el valor del item actual del QMap
        const QVector<QPointF> &data = i.value();

        // QVector::count() devuelve el numero de elementos del QVector dado
        // QPolygonF::QPolygonF(int) construye un vector de int puntos QPointF
        QPolygonF polyline(data.count());

        // Se convierte cada coordenada del Plotter en coordenadas del widget
        // y se almacena en la variable polyline. Es otra operacion complicada
        // que no voy a perder tiempo en entender de momento
        for (int j = 0; j < data.count(); ++j)
        {
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;

            double x = rect.left() +
                       (dx * (rect.width() - 1) / settings.spanX());
            double y = rect.bottom() -
                       (dy * (rect.height() - 1) / settings.spanY());

            polyline[j] = QPointF(x, y);
        }

        // Cada curva se pinta con un color diferente
        painter->setPen(colorForIds[id % 8]);

        // QPainter::drawPolyline(QPolygonF) dibuja la funcion definida por los
        // puntos de QPolygonF usando el lapiz actual
        painter->drawPolyline(polyline);
    }
}

void Plotter::cargar_configuracion()
{

    color_linea=global::color_linea;
    setPalette(colorTheme(global::color_fondo));
    refreshPixmap();

}

//////////////////////////////////////////////////////////////////////////////
void Plotter::paintEvent(QPaintEvent * /* event */ )
{
    // La clase QStylePainter extiende la clase QPainter con un conjunto de
    // funciones de dibujado de alto nivel implementadas sobre la clase QStyle
    // La clase QStyle es una clase base abstracta que encapsula la apariencia
    // y el aspecto del GUI. Qt tiene varias subclases de QStyle que emulan el
    // estilo de las distintas plataformas soportadas por Qt (QWindowsStyle,
    // QMacStyle, QMotifStyle, etc.)

    // El constructor QStylePainter::QStylePainter(QWidget) construye un
    // QStylePainter usando el widget Plotter como su QPaintDevice. Por tanto,
    // a traves de spainter dibujamos directamente sobre el widget Plotter
    QStylePainter spainter(this);

    // QPainter::drawPixmap(x,y,pixmap) copia la variable miembro pixmap sobre
    // la posicion (x,y) del widget Plotter
    spainter.drawPixmap(0, 0, pixmap);

    if (rubberBandIsShown)   // Mostrar la goma elastica
    {

        // QPainter::setPen(QPen) establece el color del lapiz con el que se
        // dibuja la goma elastica sobre el widget Plotter usando la paleta
        // light que asegura buen contraste con la paleta dark de su background
        spainter.setPen(palette().light().color());

        // QRect::normalized() devuelve un rectangulo en el que width y height
        // son valores positivos. Para ello intercambia la esquina izquierda
        // con la derecha si width < 0 y la esquina superior con la inferior
        // si height < 0

        // QRect::adjusted(x1,y1,x2,y2) devuelve un rectangulo con x1, y1, x2
        // e y2 sumadas a las coordenadas de QRect. En este ejemplo se reduce
        // en un pixel el tamaño del rectangulo normalizado

        // Notar que se dibuja directamente sobre el widget, conservando la
        // variable miembro pixmap inalterada
        spainter.drawRect(rubberBandRect.normalized().adjusted(0, 0, -1, -1));
    }

    // QWidget::hasFocus() devuelve un booleano que indica si este QWidget
    // tiene el foco de entrada del teclado
    if (hasFocus())
    {

        // La clase QStyleOptionFocusRect se usa para describir los parametros
        // necesarios para dibujar un rectangulo de foco con QStyle.
        // Debido a razones de rendimiento, el acceso a las variables miembro
        // de la clase QStyleOptionFocusRect es directo (es decir, usando los
        // operadores . o ->).
        QStyleOptionFocusRect option;

        // QStyleOptionFocusRect::initFrom(QWidget) inicializa las variables
        // miembro state, direction, rect, palette y fontMetrics basandose en
        // el QWidget especificado
        option.initFrom(this);

        // La variable miembro QStyleOptionFocusRect::backgroundColor almacena
        // el color de background con el que se dibuja el foco del rectangulo.
        option.backgroundColor = palette().dark().color();

        // QStylePainter::drawPrimitive() usa el estilo del widget definido en
        // option para dibujar un elemento QStyle::PE_FrameFocusRect, esto es,
        // un indicador de foco generico
        spainter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::resizeEvent(QResizeEvent * /* event */ )
{
    int x = width() - (zoomInButton->width() + zoomOutButton->width() + 10);

    // QWidget::move(x,y) mueve el widget a la posicion (x,y) relativa a su
    // widget padre. Los botones se separan por un hueco de 5 pixeles
    zoomInButton->move(x, 5);
    zoomOutButton->move(x + zoomInButton->width() + 5, 5);

    refreshPixmap();
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::mousePressEvent(QMouseEvent *event)
{
    // rect se corresponde con el rectangulo donde se dibuja el Plotter,
    // enmarca a los ejes de coordenadas
    QRect rect(Margin, Margin, width() - 2*Margin, height() - 2*Margin);

    if (event->button() == Qt::LeftButton)   // pulsado boton izquierdo
    {

        // QRect::contains(QPoint) devuelve true si las coordenadas del punto
        // clickado con el raton por el usuario estan dentro del rectangulo
        if (rect.contains(event->pos()))
        {

            rubberBandIsShown = true;

            // QRect::setTopLeft(QPoint) establece la esquina superior
            // izquierda del rectangulo QRect a la posicion dada, dejando
            // inalterada la esquina inferior derecha
            rubberBandRect.setTopLeft(event->pos());

            // QRect::setBottomRight(QPoint) establece la esquina inferior
            // derecha del rectangulo QRect a la posicion dada, dejando
            // inalterada la esquina superior izquierda
            rubberBandRect.setBottomRight(event->pos());

            updateRubberBandRegion();

            // QWidget::setCursor() establece la forma que tomara el cursor del
            // raton cuando este sobre el widget. La forma Qt::CrossCursor es
            // tipicamente usada para ayudar al usuario a seleccionar un punto
            // en la pantalla de manera precisa. Si no se establece ningun
            // cursor, se muestra el cursor del widget padre
            setCursor(Qt::CrossCursor);
        }
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::mouseMoveEvent(QMouseEvent *event)
{
    if (rubberBandIsShown)
    {
        updateRubberBandRegion();

        // Se recalcula el rectangulo definido para la goma elastica
        rubberBandRect.setBottomRight(event->pos());

        updateRubberBandRegion();
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
    if ((event->button() == Qt::LeftButton ) && rubberBandIsShown)
    {
        // liberado el boton izquierdo cuando se mostraba la goma elastica

        // dejar de mostrar la goma elastica
        rubberBandIsShown = false;

        // se redibuja el widget sin la goma elastica
        updateRubberBandRegion();

        // QWidget::unsetCursor() restaura la forma del cursor del raton con
        // la forma establecida por el widget padre
        unsetCursor();

        // Ajusta las coordenadas del rectangulo para evitar valores negativos
        QRect rect = rubberBandRect.normalized();

        // Una goma elastica excesivamente estrecha se ignora puesto que es muy
        // probable que haya sido seleccionada por error
        if (rect.width() < 4 || rect.height() < 4)
            return;

        // Las coordenadas del rectangulo rubberBandRect deben convertirse en
        // coordenadas para los ejes del Plotter

        // QRect::translate(x,y) desplaza el rectangulo QRect x pixeles en el
        // eje X e y pixeles en el eje Y
        rect.translate(-Margin, -Margin);

        // Se extraen las settings del nivel de zoom actual
        PlotSettings prevSettings = zoomStack[curZoom];
        PlotSettings settings;

        // QWidget::width() devuelve el ancho del widget, excluyendo cualquier
        // marco de la ventana. QWidget::height() devuelve el alto del widget,
        // excluyendo cualquier marco de la ventana

        // dx es el numero de unidades del Plotter por cada pixel del eje X
        double dx = prevSettings.spanX() / (width() - 2*Margin);

        // dy es el numero de unidades del Plotter por cada pixel del eje Y
        double dy = prevSettings.spanY() / (height() - 2*Margin);

        // QRect::left() devuelve la coordenada X del lado izquierdo del QRect
        // QRect::right() devuelve la coordenada X del lado derecho del QRect
        // QRect::bottom() devuelve la coordenada Y del lado inferior del QRect
        // QRect::top() devuelve la coordenada Y del lado superior del QRect
        settings.minX = prevSettings.minX + dx*rect.left();
        settings.maxX = prevSettings.minX + dx*rect.right();
        settings.minY = prevSettings.maxY - dy*rect.bottom();
        settings.maxY = prevSettings.maxY - dy*rect.top();

        settings.adjust();

        // Aqui las coordenadas del rectangulo rubberBandRect han sido ya
        // convertidas en coordenadas para los ejes del Plotter

        // QVector::resize() establece el nuevo tamaño del vector a curZoom+1
        zoomStack.resize(curZoom + 1);

        // QVector::push_back() añade settings al final del vector
        zoomStack.push_back(settings);

        zoomIn();
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::keyPressEvent(QKeyEvent *event)
{
    // QKeyEvent::key() devuelve un Qt::Key con el codigo de la tecla pulsada
    // o soltada
    switch (event->key())
    {
    case Qt::Key_Plus:   // tecla +
        zoomIn();
        break;
    case Qt::Key_Minus:  // tecla -
        zoomOut();
        break;
    case Qt::Key_Left:   // tecla flecha izquierda
        zoomStack[curZoom].scroll(-1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Right:  // tecla flecha derecha
        zoomStack[curZoom].scroll(+1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Down:   // tecla flecha abajo
        zoomStack[curZoom].scroll(0, -1);
        refreshPixmap();
        break;
    case Qt::Key_Up:     // tecla flecha arriba
        zoomStack[curZoom].scroll(0, +1);
        refreshPixmap();
        break;
    default:             // Se procesa la tecla pulsada por el widget padre
        QWidget::keyPressEvent(event);
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::wheelEvent(QWheelEvent *event)
{
    // QWheelEvent::delta() devuelve la distancia que ha rotado la rueda (un
    // grado cada 8 unidades delta)
    int numDegrees = event->delta() / 8;

    // Numero de marcas desplazadas en el eje en funcion del numero de grados
    int numTicks = numDegrees / 15;

    // QWheelEvent::orientation() devuelve la orientacion de la rueda que ha
    // girado (la mayoria de ratones solo proporcionan una rueda vertical)
    if (event->orientation() == Qt::Horizontal)
        zoomStack[curZoom].scroll(numTicks, 0);
    else
        zoomStack[curZoom].scroll(0, numTicks);

    refreshPixmap();
}


//////////////////////////////////////////////////////////////////////////////
void Plotter::zoomOut()
{
    if (curZoom > 0)   // Al menos hay un zoom in y podemos hacer zoom out
    {
        --curZoom;

        // QWidget::setEnabled(bool) establece si el widget esta o no enabled
        // (solo los widget enabled manejan los eventos de teclado y raton)
        zoomOutButton->setEnabled(curZoom > 0);
        zoomInButton->setEnabled(true);

        zoomInButton->show();

        refreshPixmap();
    }
}



//////////////////////////////////////////////////////////////////////////////
void Plotter::zoomIn()
{
    // QVector::count() devuelve el numero de items del vector
    if (curZoom < zoomStack.count() - 1)
    {
        ++curZoom;

        zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
        zoomOutButton->setEnabled(true);

        zoomOutButton->show();

        refreshPixmap();
    }
}
//funcion para cambiar los label
void Plotter::SetLabel(QString Tit,QString xl, QString yl)
{
    this->Titulo=Tit;
    this->Xlabel=xl;
    this->Ylabel=yl;
}
